#include <Ticker.h>
#include <typeinfo>

#include "ch/bsp.h"
#include "config.h"
#include "modules/json_analysis/json_analysis.hpp"
#include "app/camera/avi.h"
#include "app/camera/app_http.h"
#include "app/hardware.h"
#include "app/ev_json_analysis.h"
#include "app/camera/photo.h"
#include "modules/json_analysis/json_mapping.hpp"
#include "modules/AIGCJson/include/AIGCJson.hpp"
#include "modules/json_analysis/json_entity.h"

using namespace aigc;

/* define ---------------------------------------------------------------------------------------------------------------*/
#define LED_BUILTIN 33

#define soft_feed_dog() (resetTim_c = 0)
/* define ---------------------------------------------------------------------------------------------------------------*/

/* variant ---------------------------------------------------------------------------------------------------------------*/
FtpServer ftpSrv; //set #define FTP_DEBUG in ESP32FtpServer.h to see ftp verbose on serial
EvJSON_Analysis jsona;
JSON_Analysis<JSONA_BUFSIZE> jsonCfg;
JSON_ConfigEntity configEntity;

Ticker resetTim;

const IPAddress serverIP(REMOTE_IP); //remote ip address
uint16_t serverPort = REMOTE_PORT;   //remote port
uint16_t machineID = MACHINE_ID;

WiFiClient serverClient;
uint32_t resetTim_c = 0;

TaskHandle_t jsonCfgRecvHandle = NULL;

uint32_t reportTime_c = TIME_TO_REPORT_SERVER;

/* variant ---------------------------------------------------------------------------------------------------------------*/
void resetTimInterrupt()
{
    resetTim_c++;
    if (resetTim_c > RESET_TIM_CNT)
    {
        logln("soft dog trigger,reset...");
        Serial.flush();
        delay(200);
        ESP.restart();
    }
    if (reportTime_c != 0)
    {
        reportTime_c--;
        logf("%u ", reportTime_c);
    }
}

void jsonCfgRecvTask(void *pvParameters)
{
    while (1)
    {
        // static uint32_t cfg_cnt = millis();
        // if (millis() - cfg_cnt > 500)
        {
            // vTaskEnterCritical(&mux);
            bool isok = jsonCfg.loop();
            if (isok)
            {
                logf("reviced json :%s\r\n", jsonCfg._jsonBuf);
                bool isok = JsonHelper::JsonToObject(configEntity, jsonCfg._jsonBuf);
                if (isok)
                    // logf("received json config [%s] \r\n", (char *)jsonCfg._jsonBuf);
                    logln("object convert success!!!");
                else
                    logln("object convect failed!!!");
            }
            // cfg_cnt = millis();
            // vTaskExitCritical(&mux);
        }
        vTaskDelay(100);
    }
}
void setup()
{
    // Serial.begin(9600);          //�����ظ����ò����ʣ�����ᵼ�³�����?
    jsona.begin(&Serial, 9600); //
    jsonCfg.begin(&Serial2, 9600);

    Serial.setDebugOutput(true);

    logln("system begin...");

    // zzz
    logln("                                    ");
    logln("-------------------------------------");
    logln("ESP-CAM Video Recorder v23");
    logln(" ip 192.168.10.222 ");
    logln("-------------------------------------");

    pinMode(LED_BUILTIN, OUTPUT);   // little red led on back of chip
    digitalWrite(LED_BUILTIN, LOW); // turn on the red LED on the back of chip

    if (psramFound())
    {
        logln("psram founed!");
    }

    resetTim.attach(1, resetTimInterrupt);

    init_hardware();

    // 200 ms x 150 frames = 30 seconds     is 3 MB indoors
    // 200 ms x 300 frames = 1 minute       is about 6MD indoor
    // 20 ms x 3000 frames = 10 minute      is 60 MB indoor
    // burst 1000 frames gives 8 fps rather than 5, so 2 minutues 20 MB indoor
    // startCameraServer();

    log("Camera Ready! Use 'http://");
    log(WiFi.localIP());

    sprintf(localip, "%s", WiFi.localIP().toString().c_str());
    log("localip ");
    logln(localip);

    // zzz username and password for ftp server
    ftpSrv.begin("esp", "esp");
    digitalWrite(LED_BUILTIN, HIGH);

    // if (serverClient.connect(serverIP, serverPort))
    //     logln("conenct to remote server succeed!");
    // else
    //     logln("connect remote server failed,power restart"), ESP.restart();

    xTaskCreateUniversal(jsonCfgRecvTask, "jsonCfgRecvTask", (5 * 1024), NULL, 1, &jsonCfgRecvHandle, CONFIG_ARDUINO_RUNNING_CORE);
    logf("free head=%u,free stack=%u \r\n", ESP.getFreeHeap(), ESP.getFreeSketchSpace());
}

long wakeup;
long last_wakeup = 0;

uint32_t capturePhoto_c = 0;

String photoFilename;
String videoFilename;

bool mqttPulish_f = false;
bool videoAllowRecord_f = true, photoAllowCapture_f = true, environmentAllow_f = true;
bool videoRecored_f = false, photoCaptured_f = false, environmentCollectioned_f = false;

uint32_t report2server_c = 0;

bool connectServer()
{
    bool isok = false;
    uint8_t try_cnt = 0;
    do
    {
        try_cnt++;
        isok = serverClient.connect(serverIP, serverPort);
        if (isok == false)
        {
            logf("retry connect server count %d \r\n", try_cnt);
        }
        if (try_cnt > 3)
        {
            return false;
        }
    } while (!isok);

    return true;
}

void reportData2Server()
{
    if (connectServer())
        logln("connect server is OK!");
    else
        logln("connect server is failed,restart!"), Serial.flush(), ESP.restart();
    {
        /* transmit environment ---------------------------------------------------------------------------------------------------------------*/
        // logln("transmit environment begin...");
        // String evJsonStr;
        // evJsonStr += "{";
        // char idNode_buf[32] = {0};
        // sprintf(idNode_buf, "\"id\":%04d", machineID);
        // evJsonStr += idNode_buf;
        // /* mahine ID ---------------------------------------------------------------------------------------------------------------*/
        // evJsonStr += ",";
        // String jsonStringPrefix = "\"environment\":";
        // evJsonStr += jsonStringPrefix;
        // evJsonStr += (char *)jsona.payloadBuf;
        // evJsonStr += "}";
        // serverClient.write(evJsonStr.c_str(), evJsonStr.length());
        // serverClient.write("#", 1);

        logln("transmit environment begin...");
        /*
            {
                    "payload":
                        {
                            "smartBuckles":[{"temperature":0,"heartbeat":0,"finger_f":false}],
                            "bme280":{"temperature":33,"humidity":60.11621,"altitude":61.43446,"pressure":1005.893},
                            "m702":{"CO2":0,"CH2O":0,"TVOC":0,"PM25":0,"PM10":0,"temperature":0,"humidity":0},
                            "NH":0
                        },
                "checksum":18687
            }
        */
        JsonObject payloadJsonObject = jsona._root["payload"];
        JsonObject bme280JsonObject = jsona._root["payload"]["bme280"];
        JsonObject m702JsonObject = jsona._root["payload"]["m702"];

        float pig_temp = 0; // ������
        int heartbeat = 0;  //����
        // float temperature = bme280JsonObject["temperature"]; //����
        // float humidity = bme280JsonObject["humidity"];       // ʪ��

        float pressure = bme280JsonObject["pressure"]; //��ѹ
        float altitude = bme280JsonObject["altitude"]; //����

        float temperature = m702JsonObject["temperature"]; //����
        temperature -= 5.5; //����
        float humidity = m702JsonObject["humidity"];         // ʪ��
        float pm25 = m702JsonObject["PM25"];                 //PM2.5
        float pm10 = m702JsonObject["PM10"];                 //PM10
        float co2 = m702JsonObject["CO2"];                   //CO2
        float tvoc = m702JsonObject["TVOC"];                 //TVOC
        float ch2o = m702JsonObject["CH2O"];                 //��ȩ
        float nh3 = payloadJsonObject["NH"];                 // ����

        String out = "";
        out += "[";
        char temp[128] = {0};

        char *formart;

        formart = "{\"id\":\"%d\",\"type\":\"pig_temp\",\"value\":\"%.1f\"},";
        sprintf(temp, formart, machineID, pig_temp);
        out += temp;
        memset(temp, 0, sizeof(temp));

        formart = "{\"id\":\"%d\",\"type\":\"heartbeat\",\"value\":\"%d\"},";
        sprintf(temp, formart, machineID, heartbeat);
        out += temp;
        memset(temp, 0, sizeof(temp));

        formart = "{\"id\":\"%d\",\"type\":\"temperature\",\"value\":\"%.1f\"},";
        sprintf(temp, formart, machineID, temperature);
        out += temp;
        memset(temp, 0, sizeof(temp));

        formart = "{\"id\":\"%d\",\"type\":\"humidity\",\"value\":\"%.1f\"},";
        sprintf(temp, formart, machineID, humidity);
        out += temp;
        memset(temp, 0, sizeof(temp));

        formart = "{\"id\":\"%d\",\"type\":\"pressure\",\"value\":\"%.1f\"},";
        sprintf(temp, formart, machineID, pressure);
        out += temp;
        memset(temp, 0, sizeof(temp));

        formart = "{\"id\":\"%d\",\"type\":\"altitude\",\"value\":\"%.1f\"},";
        sprintf(temp, formart, machineID, altitude);
        out += temp;
        memset(temp, 0, sizeof(temp));

        formart = "{\"id\":\"%d\",\"type\":\"PM25\",\"value\":\"%.1f\"},";
        sprintf(temp, formart, machineID, pm25);
        out += temp;
        memset(temp, 0, sizeof(temp));

        formart = "{\"id\":\"%d\",\"type\":\"PM10\",\"value\":\"%.1f\"},";
        sprintf(temp, formart, machineID, pm10);
        out += temp;
        memset(temp, 0, sizeof(temp));

        formart = "{\"id\":\"%d\",\"type\":\"CO2\",\"value\":\"%.1f\"},";
        sprintf(temp, formart, machineID, co2);
        out += temp;
        memset(temp, 0, sizeof(temp));

        formart = "{\"id\":\"%d\",\"type\":\"TVOC\",\"value\":\"%.1f\"},";
        sprintf(temp, formart, machineID, tvoc);
        out += temp;
        memset(temp, 0, sizeof(temp));

        formart = "{\"id\":\"%d\",\"type\":\"CH2o\",\"value\":\"%.1f\"},";
        sprintf(temp, formart, machineID, ch2o);
        out += temp;
        memset(temp, 0, sizeof(temp));

        formart = "{\"id\":\"%d\",\"type\":\"NH\",\"value\":\"%.1f\"}";
        sprintf(temp, formart, machineID, nh3);
        out += temp;
        memset(temp, 0, sizeof(temp));

        out += "]#";
        serverClient.write((uint8_t *)(out.c_str()), out.length());
        logln(out.c_str());
        logln("transmit environment end...");

    } /* transmit environment ---------------------------------------------------------------------------------------------------------------*/

    for (int i = 0; i < 10; i++)
        vTaskDelay(500);

    if (connectServer())
        logln("connect server is OK!");
    else
        logln("connect server is failed,restart!"), Serial.flush(), ESP.restart();
    { /* transmit photo ---------------------------------------------------------------------------------------------------------------*/
        logln("transmit jpg begin...");
        /* calculate send photo data size ---------------------------------------------------------------------------------------------------------------*/
        uint32_t photoBufSize = 0, photoTotalSize = 0;
        String jsonString = R"({"jpg":[\"]})";
        uint32_t photoFileSize = get_file_size(photoFilename.c_str());
        logf("photo file name = %s ,size = %d\r\n", photoFilename.c_str(), photoFileSize);
        /* calculate send photo data size ---------------------------------------------------------------------------------------------------------------*/
        serverClient.write("{", 1); //json seg
        // logf("{");
        photoTotalSize += 1;
        FILE *file = fopen(photoFilename.c_str(), "r");
        static uint8_t buffer[FRAME_SIZE] = {0};
        String jsonStringPrefix = "";
        char str[64] = {0};
        sprintf(str, "\"id\":\"%d\",\"jpg\":[\"", machineID);
        jsonStringPrefix += str;
        serverClient.write(jsonStringPrefix.c_str(), jsonStringPrefix.length());
        // logf(jsonStringPrefix.c_str());
        photoTotalSize += jsonStringPrefix.length();
        uint32_t sendFrame_c = 0;
        bool isok = false;
        while (1)
        {
            if (feof(file))
            {
                // remove(path); //delete file archive
                logln("\r\nphoto transimit finish!");
                break;
            }
            memset(buffer, 0, FRAME_SIZE);
            uint32_t len = fread(buffer, 1, FRAME_SIZE, file);
            String jsonString = "";
            for (int i = 0; i < len; i++)
            {
                char temp[4] = {0};
                sprintf(temp, "%02x,", buffer[i]);
                jsonString += temp;
            }
            isok = serverClient.write(jsonString.c_str(), jsonString.length());
            // logf(jsonString.c_str());
            photoTotalSize += jsonString.length();
            if (isok == false)
            {
                logln("send photo failed,abort file transimit!");
                if (serverClient.connected() == false)
                {
                    logln("socket not connect");
                }
                fclose(file);
                // break;
                return;
            }
            else
            {
                logf("%02d ", sendFrame_c++);
            }
        }

        String jsonStringSubfix = "]";
        serverClient.write(jsonStringSubfix.c_str(), jsonStringSubfix.length());
        // logf(jsonStringSubfix.c_str());
        photoTotalSize += jsonStringSubfix.length();
        fclose(file);
        serverClient.write("}#", 2); //json seg
        // logf("}#");
        photoTotalSize += 1;
        logln("transmit jpg end...");
        logf("photo send size=%u,total size=%u\r\n", photoBufSize, photoTotalSize);
    } /* transmit photo ---------------------------------------------------------------------------------------------------------------*/

    for (int i = 0; i < 10; i++)
        vTaskDelay(500);

    if (connectServer())
        logln("connect server is OK!");
    else
        logln("connect server is failed,restart!"), Serial.flush(), ESP.restart();
    /* transmit avi ---------------------------------------------------------------------------------------------------------------*/
    {
        logln("transmit avi begin...");
        /* calculate send video data size ---------------------------------------------------------------------------------------------------------------*/
        uint32_t videoBufSize = 0, videoTotalSize = 0;
        String jsonString = R"({"avi":[\"]})";
        uint32_t videoFileSize = get_file_size(videoFilename.c_str());
        logf("video file name = %s ,size = %d\r\n", videoFilename.c_str(), videoFileSize);
        // videoBufSize = jsonString.length() + videoFileSize * 3;
        // serverClient.write((uint8_t *)&videoBufSize, 4); //�������ݰ�����
        /* calculate send video data size ---------------------------------------------------------------------------------------------------------------*/

        serverClient.write("{", 1); //json seg
        videoTotalSize += 1;
        FILE *file = fopen(videoFilename.c_str(), "r");
        static uint8_t buffer[FRAME_SIZE] = {0};
        // String jsonStringPrefix = "\"avi\":[";
        String jsonStringPrefix = "";
        char str[64] = {0};
        sprintf(str, "\"id\":\"%d\",\"avi\":[\"", machineID);
        jsonStringPrefix += str;
        serverClient.write(jsonStringPrefix.c_str(), jsonStringPrefix.length());
        videoTotalSize += jsonStringPrefix.length();
        uint32_t sendFrame_c = 0;
        bool isok = false;
        while (1)
        {
            if (feof(file))
            {
                // remove(path); //delete file archive
                logln("\r\nvideo transimit finish!");
                if (serverClient.connected() == false)
                {
                    logln("socket not connect");
                }
                break;
            }
            memset(buffer, 0, FRAME_SIZE);
            uint32_t len = fread(buffer, 1, FRAME_SIZE, file);
            String jsonString = "";
            for (int i = 0; i < len; i++)
            {
                char temp[4] = {0};
                sprintf(temp, "%02x,", buffer[i]);
                jsonString += temp;
            }
            isok = serverClient.write(jsonString.c_str(), jsonString.length());
            videoTotalSize += jsonString.length();
            if (isok == false)
            {
                logln("send data failed,abort file transimit!");
                fclose(file);
                //break;
                return;
            }
            else
            {
                logf("%02d ", sendFrame_c++);
            }
        }
        String jsonStringSubfix = "]";
        serverClient.write(jsonStringSubfix.c_str(), jsonStringSubfix.length());
        videoTotalSize += jsonStringSubfix.length();
        logln("transmit avi end...");
        fclose(file);

        serverClient.write("}", 1);
        videoTotalSize += 1;

        if (isok)
            serverClient.write("#", 1);
        logf("video send size=%u,total size=%u\r\n", videoBufSize, videoTotalSize);
    }
    for (int i = 0; i < 10; i++)
        vTaskDelay(500);

    /* transmit avi ---------------------------------------------------------------------------------------------------------------*/
    // serverClient.stop(); //�����ǶϿ�����
}

uint32_t runTick_c = millis();

portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
void loop()
{
    if (WiFi.status() != WL_CONNECTED)
    {
        init_wifi();
        logln("***** WiFi reconnect *****");
    }

    // ftpSrv.handleFTP();
    if ((environmentAllow_f) && (environmentCollectioned_f == false))
    {
        jsona.loop();
    }

    if ((photoAllowCapture_f) && (photoCaptured_f == false))
    {
        save_photo();
    }
    if ((videoAllowRecord_f) && (videoRecored_f == false))
    {
        make_avi();
    }

    static bool reportCountDown_f = false;
    static uint32_t reportCountDown_c = 0;

    static uint8_t f = 1;
    //{} video,photo,evironment collectioned ,report data to server
    if (videoRecored_f && photoCaptured_f && environmentCollectioned_f)
    // if (videoRecored_f && photoCaptured_f)
    {
        // if (((millis() - reportTime_c) > TIME_TO_REPORT_SERVER) || (reportTime_c == 0)) //time to report data
        if ((reportTime_c == 0) || (f)) //time to report data
        {
            f = 0;
            reportData2Server();
            reportCountDown_f = true, reportCountDown_c = TIME_TO_REPORT_SERVER / 1000;
            logln("report data count down begin...");
            videoAllowRecord_f = true, photoAllowCapture_f = true, environmentAllow_f = true;
            videoRecored_f = false, photoCaptured_f = false, environmentCollectioned_f = false;
            reportTime_c = TIME_TO_REPORT_SERVER;
        }
    }

    if (reportCountDown_f)
    {
        if ((millis() % 1000 == 0) && (reportCountDown_c > 0))
            logf("%d ", --reportCountDown_c);
        if (reportCountDown_c == 0)
        {
            logln("\r\nreport to server ready");
            reportCountDown_f = false;
        }
    }

    soft_feed_dog(); //soft dog feed

    static uint32_t led_count = millis();
    if (millis() - led_count > 100)
    {
        digitalRead(LED_BUILTIN) == 0 ? digitalWrite(LED_BUILTIN, HIGH) : digitalWrite(LED_BUILTIN, LOW);
        led_count = millis();
    }
    runTick_c = millis();
}
